02 / 04

What is the difference between microtasks and macrotasks?

JavaScript's event loop maintains multiple queues for handling asynchronous operations, with the most fundamental distinction being between microtasks and macrotasks. This prioritization system ensures that certain types of callbacks (like promise resolutions) are executed as soon as possible, while others (like setTimeout or I/O events) are deferred. Understanding this difference is crucial for predicting execution order and avoiding subtle bugs in asynchronous code.

Macrotasks (Tasks)
  1. 1

    Definition: Macrotasks are larger units of work that the event loop picks from its queue to execute one per iteration. Each macrotask runs to completion before moving to the next .

  2. 2

    Examples: setTimeout, setInterval, setImmediate (Node.js), I/O operations, UI rendering (browser), requestAnimationFrame .

  3. 3

    Execution Order: The event loop picks the oldest macrotask from the queue and executes it entirely. After it completes, it checks the microtask queue before proceeding to the next macrotask or rendering .

  4. 4

    Creation Sources: Typically scheduled by the host environment (browser or Node.js) in response to external events, timers, or I/O completion .

Microtasks (Jobs)
  1. 1

    Definition: Microtasks are smaller, high-priority tasks that need to be executed immediately after the currently executing script, before the next macrotask or rendering .

  2. 2

    Examples: Promise callbacks (.then, .catch, .finally), MutationObserver, queueMicrotask(), process.nextTick (Node.js, though technically a separate queue with even higher priority) .

  3. 3

    Execution Order: After every macrotask, the event loop processes the entire microtask queue until it's empty. If new microtasks are added during this processing, they are executed in the same cycle .

  4. 4

    Creation Sources: Usually generated by JavaScript itself rather than external events, such as promise resolutions or explicit queueMicrotask calls .

Microtask vs Macrotask Execution Order

The event loop algorithm for browsers and Node.js follows this pattern: execute the oldest macrotask → process all microtasks → perform rendering if needed → next macrotask. This ensures that microtask callbacks run before any I/O events, timers, or rendering, which is essential for promise-based APIs to maintain predictable state consistency. For example, when a promise resolves, its .then callback should run before any new network events are processed, preventing race conditions.

Key Differences
  1. 1

    Priority: Microtasks always have higher priority than macrotasks. They run immediately after the current script completes, even before rendering .

  2. 2

    Queue Processing: The event loop processes ONE macrotask per iteration, but processes ALL microtasks until the microtask queue is empty .

  3. 3

    Recursive Microtasks: If a microtask schedules another microtask, the event loop will keep processing them in the same cycle, potentially starving macrotasks .

  4. 4

    Rendering Interleaving: In browsers, rendering occurs between macrotask and microtask processing. If you need to update the UI after state changes, microtasks may delay rendering .

  5. 5

    Error Handling: Errors in microtasks and macrotasks propagate differently. Unhandled promise rejections (microtasks) are treated specially, while macrotask errors can be caught with try/catch in the appropriate scope .

Microtask Starvation Example

In Node.js, the distinction is slightly more nuanced. It uses multiple phases within its event loop: timers, I/O callbacks, idle/prepare, poll, check (setImmediate), and close callbacks. Microtasks (including process.nextTick which has its own queue with even higher priority) are processed between these phases. Specifically, after each phase, Node.js processes all process.nextTick callbacks, then all other microtasks (promise callbacks), before moving to the next phase.

Node.js Event Loop Phases with Microtasks

For developers, the practical implication is that code using promises will generally run before code using setTimeout, even with zero delay. This is why microtasks are perfect for promise-based APIs (they execute as soon as possible) and why setTimeout is suitable for deferring work that shouldn't block the microtask queue. Understanding this queue prioritization helps in debugging race conditions and optimizing performance, especially when mixing promises with timers or I/O operations.

Difficulty: 6/10
Topics: event loop, task queues, promise

Scenario Questions

0-2 years experience
  1. 1

    If you call setTimeout(fn, 0) and then Promise.resolve().then(fn2), which callback runs first and why?

  2. 2

    What happens to a promise's .then handler if the JavaScript thread is busy with a long‑running loop?

  3. 3

    Where would console.log statements appear when you mix setTimeout, requestAnimationFrame, and a resolved promise?

2-5 years experience
  1. 1

    We noticed a UI freeze after a series of async fetches; debugging shows many microtasks queued. How would you restructure the code to avoid starving the macrotask queue?

  2. 2

    During a feature rollout, a bug appears where a state update inside a promise's .then runs after a setTimeout that should have executed earlier. Walk me through how the event‑loop ordering could cause this.

  3. 3

    If you need cleanup code to run after all pending UI updates but before the next repaint, which queue would you use and why?

5-8 years experience
  1. 1

    Design a throttling mechanism for a high‑frequency event (e.g., scroll) that balances microtask overhead and UI responsiveness. Explain your choice of task vs microtask scheduling.

  2. 2

    In a large app, you have a custom scheduler that batches work using Promise.resolve().then. What are the scalability concerns and how would you mitigate potential starvation of macrotasks like I/O?

  3. 3

    Explain how using async/await in a server‑side Node.js service could impact the order of I/O callbacks under heavy load, and propose a pattern to keep latency predictable.

8+ years experience
  1. 1

    Our front‑end framework plans to migrate from a callback‑heavy architecture to a promise‑based one. What architectural considerations around microtask vs macrotask ordering should we account for to avoid subtle bugs across teams?

  2. 2

    When integrating a third‑party library that relies on setTimeout(...,0) for deferring work, how would you assess the risk of microtask queue overload in a high‑traffic SaaS, and what cross‑team guidelines would you establish?

  3. 3

    Describe a strategy for a long‑term codebase to standardize async flow so that microtasks don't unintentionally delay critical macrotasks like network I/O, especially when multiple teams contribute.

Follow-up Questions

  • How does this ordering affect UI responsiveness?
  • What happens if a microtask throws an uncaught error?
  • Can you give an example where using the wrong queue caused a bug?